﻿using System;
using System.Collections.Generic;
using System.Linq;
using Monopoly.Exceptions;

namespace Monopoly.Domain
{
    public class Player : BoxPropertyOwner
    {
        private string Name;
        private Piece Piece;
        private int Position;
        private int Cash;
        private bool IsInJail;
        private short RoundsInJail;
        private Game Game;

        private IList<BoxProperty> Properties;
        private IList<Card> Cards;

        /// <summary>
        /// Initialize a new player with the given name
        /// </summary>
        /// <param name="Name">Name of the player</param>
        /// <param name="Piece">Name of the player's piece on the gameboard</param>
        /// <param name="Game">The Game where the player is going to play</param>
        public Player(string Name, Piece Piece, Game Game)
        {
            this.Name = Name;
            this.Piece = Piece;
            this.Game = Game;
            this.Cash = 150000;

            this.Position = 1;
            this.IsInJail = false;
            this.RoundsInJail = 0;

            this.Properties = new List<BoxProperty>();
            this.Cards = new List<Card>();
        }

        /// <summary>
        /// Check if the player is in jail
        /// </summary>
        /// <returns><code>true</code> if the player is in jail</returns>
        public Boolean GetIsInJail()
        {
            return this.IsInJail;
        }

        /// <summary>
        /// Return the rounds in jail of the player
        /// </summary>
        /// <returns><code>short</code></returns>
        public short GetRoundsInJail()
        {
            return this.RoundsInJail;
        }

        public void IncrementRoundsInJail()
        {
            this.RoundsInJail++;
        }

        /// <summary>
        /// Return the name of the player
        /// </summary>
        /// <returns><code>string</code></returns>
        public string GetName()
        {
            return this.Name;
        }

        public IList<BoxProperty> GetProperties()
        {
            IList<BoxProperty> Result = new List<BoxProperty>();
            foreach (BoxProperty p in this.Properties)
                Result.Add(p);

            return Result;
        }

        /// <summary>
        /// Move the player to a specific box
        /// </summary>
        /// <param name="BoxIndex">The index of the box</param>
        /// <returns><code>Box</code></returns>
        public Box ChangePosition(int BoxIndex, bool GoingToJail = false, bool StandBack = false)
        {
            Box Box = this.Game.GetGameboard().GetBox(BoxIndex);
            Program.WriteLine("Vous êtes arrivés sur " + Box.GetName(true));

            // Pass over go
            if (!GoingToJail && this.Position > BoxIndex)
            {
                Program.WriteLine("Vous êtes passés par la case départ, recevez " + (20000).ToEuro(true));
                this.Game.GetBank().GetPassOverGoMoney(this);
            }

            this.Position = BoxIndex;

            return Box;
        }

        /// <summary>
        /// Move the player to a new box accroding to the parameter value and his previous position
        /// </summary>
        /// <param name="Move">The move</param>
        /// <returns><code>Box</code></returns>
        public Box MovePlayer(int Move, bool StandBack = false)
        {
            int NewPosition = this.Position + Move;
            if (NewPosition > 40)
                NewPosition -= 40;
            return this.ChangePosition(NewPosition, false, StandBack);
        }

        public void GoToJail()
        {
            Program.WriteLine("Vous allez en prison");
            this.ChangePosition(11, true);
            this.IsInJail = true;
        }

        public void GetOutOfJail()
        {
            Program.WriteLine("Vous sortez de prison");
            this.IsInJail = false;
            this.RoundsInJail = 0;
        }

        public Chance PickAChanceCard()
        {
            Chance ChanceCard = this.Game.GetGameboard().PopChanceCard();
            this.Game.GetGameboard().PushChanceCard(ChanceCard);
            return ChanceCard;

        }

        public CommunityChest PickACommunityChestCard()
        {
            CommunityChest CommunityChestCard = this.Game.GetGameboard().PopCommunityChestCard();
            this.Game.GetGameboard().PushCommunityChestCard(CommunityChestCard);
            return CommunityChestCard;
        }

        /// <summary>
        /// Remove and returns all of the player's real estate
        /// </summary>
        /// <returns>A list of all old real estate of the players</returns>
        /// <remarks>Player's old real estate goes back to the bank</remarks>
        public IList<RealEstate> RemoveRealEstates()
        {
            IList<RealEstate> RealEstates = new List<RealEstate>();
            foreach (Field BoxProperty in this.Properties.Where(bp => bp is Field))
                foreach (RealEstate RealEstate in BoxProperty.RemoveRealEstates())
                    RealEstates.Add(RealEstate);
            return RealEstates;
        }

        /// <summary>
        /// Remove and returns the property list of the player
        /// </summary>
        /// <returns>Player's old property list</returns>
        /// <remarks>Player's properties goes back to the bank</remarks>
        public IList<BoxProperty> RemoveProperties()
        {
            IList<BoxProperty> PlayerProperties = this.Properties;
            this.Properties = new List<BoxProperty>();
            return PlayerProperties;
        }

        public void AddCard(Card Card)
        {
            this.Cards.Add(Card);
        }

        public bool HasOutOfJailCard()
        {
            return this.Cards.Count != 0;
        }

        public void RemoveOutOfJailCard()
        {
            if (this.HasOutOfJailCard())
                this.Cards.RemoveAt(0);
        }

        /// <summary>
        /// Gives back old player's cards
        /// </summary>
        /// <returns>Player's walk free out of jail cards</returns>
        /// <remarks>Cards can be chance or community chest. They are going back to the right stack</remarks>
        public IList<Card> RemoveCards()
        {
            IList<Card> PlayerCards = this.Cards;
            this.Cards = new List<Card>();
            return PlayerCards;
        }

        /// <summary>
        /// Gets the list of fields that contains real estate of the given type.
        /// The list contains only fields with salable houses
        /// </summary>
        /// <param name="Type">Type of wanted real estate</param>
        /// <returns>List of fields</returns>
        /// <value>Type=House|Hotel</value>
        public IList<Field> GetFieldWithRealEstate(Type Type)
        {
            IList<BoxProperty> Properties = (IList<BoxProperty>)this.Properties.Where(p => p is Field && ((Field)p).HasRealEstate(Type)).ToList();
            foreach (BoxProperty p in Properties)
            {
                Field f = (Field)p;
                IList<int> StreetProperties = this.Properties.Where(x => x is Field && ((Field)x).GetColor() == f.GetColor() && ((Field)x).GetName() != f.GetName()).Select(x => ((Field)x).GetHouseCount()).ToList();
                int CurProperty = f.GetHouseCount();
                int MinProperty = StreetProperties.Min();
                int MaxProperty = StreetProperties.Max();
                if (CurProperty < MinProperty || (CurProperty == MinProperty && MinProperty < MaxProperty))
                    Properties.Remove(p);
            }
            IList<Field> Fields = new List<Field>();
            foreach (BoxProperty p in Properties)
                Fields.Add((Field)p);
            return Fields;
        }

        /// <summary>
        /// Remove a property to the player
        /// </summary>
        /// <param name="BoxToRemove">Box to remove</param>
        public void RemoveProperty(BoxProperty BoxToRemove)
        {
            this.Properties.Remove(BoxToRemove);
        }

        /// <summary>
        /// Add a property to the player
        /// </summary>
        /// <param name="BoxToAdd">Box to add</param>
        public void AddProperty(BoxProperty BoxToAdd)
        {
            this.Properties.Add(BoxToAdd);
        }

        /// <summary>
        /// Add cash to the player
        /// </summary>
        /// <param name="Amount">Amount to add</param>
        public void AddCash(int Amount)
        {
            this.Cash += Amount;
        }

        /// <summary>
        /// Withdraw cash from player's account
        /// </summary>
        /// <param name="Amount">Amount to withdraw</param>
        /// <remarks>Cancel opération if the player doesn't have enough money</remarks>
        /// <exception cref="OutOfCashException">The player account doesn't have enough founds</exception>
        public void WithdrawCash(int Amount)
        {
            if (Amount <= this.Cash)
                this.Cash -= Amount;
            else
                throw new OutOfCashException();
        }

        public bool HasEnoughCash(int Amount)
        {
            if (this.Cash - Amount < 0)
                return false;
            else
                return true;
        }

        public bool BuyFreeProperty(BoxProperty BoxProperty)
        {
            if (!this.HasEnoughCash(BoxProperty.GetPrice()))
            {
                return false;
            }
            else
            {
                Program.WriteLine("\nVous avez acheté " + BoxProperty.GetName(true) + " pour " + BoxProperty.GetPrice().ToEuro(false));
                this.WithdrawCash(BoxProperty.GetPrice());
                Game.GetBank().GiveProperty(BoxProperty, this);
                return true;
            }
        }

        public void BuyPropertyFromBid(BoxProperty BoxProperty, int BidAmount)
        {
            Program.WriteLine(this.Name + " remporte l'enchère. Il achète la propriété " + BoxProperty.GetName(true) + " pour " + BidAmount.ToEuro());
            this.WithdrawCash(BidAmount);
            Game.GetBank().GiveProperty(BoxProperty, this);
        }

        public bool PayBankForFreeParking(int Amount)
        {
            if (!this.HasEnoughCash(Amount))
            {
                return false;
            }
            else
            {
                Program.WriteLine("Vous avez payé " + Amount.ToEuro(false) + " au Parc Gratuit");
                this.WithdrawCash(Amount);
                this.Game.GetBank().AddCashOnFreeParking(Amount);
                return true;
            }
        }

        public int GetCash()
        {
            return this.Cash;
        }

        /// <summary>
        /// Pay the target player
        /// </summary>
        /// <param name="Target">Target player</param>
        /// <param name="Amount">Amount to withdraw from the player and amount to add to the target</param>
        public Boolean PayPlayer(Player Target, int Amount)
        {
            if (!this.HasEnoughCash(Amount))
            {
                throw new OutOfCashException();
            }
            else
            {
                Program.WriteLine(this.GetName() + " a payé " + Amount.ToEuro(false) + " à " + Target.GetName());
                this.WithdrawCash(Amount);
                Target.AddCash(Amount);
                return true;
            }
        }

        public Boolean SellProperty(Player Target, BoxProperty Box)
        {
            if (Target.PayPlayer(this, Box.GetPrice()))
            {
                Box.SetOwner(Target);
                Target.AddProperty(Box);
                this.RemoveProperty(Box);
                return true;
            }
            else
                return false;
        }

        public bool HasAllFieldsByColor(ConsoleColor ConsoleColor)
        {
            return this.Game.GetGameboard().CountFieldsByColor(ConsoleColor) == this.CountFieldsdByColor(ConsoleColor);
        }

        /// <summary>
        /// Computes and return the list of fields that match the given color
        /// </summary>
        /// <param name="Color"></param>
        /// <returns></returns>
        public IList<Field> GetFieldListByColor(ConsoleColor Color)
        {
            var Properties = this.Properties.Where(p => ((Field)p).GetColor() == Color);
            IList<Field> Fields = new List<Field>(Properties.Count());
            foreach (BoxProperty Property in Properties)
                Fields.Add((Field)Property);
            return Fields;
        }

        public int CountFieldsdByColor(ConsoleColor Color)
        {
            return this.Properties.Where(p => p is Field && ((Field)p).GetColor() == Color).Count();
        }

        public int CountStation()
        {
            return this.Properties.Where(p => p is Station).Count();
        }

        public bool HasBothCompanies()
        {
            return this.Properties.Where(p => p is Company).Count() == 2;
        }

        /// <summary>
        /// Gets the list of fields on which the given real estate type can be add
        /// </summary>
        /// <param name="Type">Type of real estate</param>
        /// <returns>List of fields</returns>
        public IList<Field> CanAddRealEstateList(Type Type)
        {
            IList<BoxProperty> Properties;
            if (Type == typeof(Hotel))
                Properties = this.Properties.Where(p => p is Field && ((Field)p).GetHouseCount() == 4).ToList<BoxProperty>();
            else
                Properties = this.Properties.Where(p => p is Field && ((Field)p).GetHouseCount() < 4).ToList<BoxProperty>();

            IList<Field> Fields = new List<Field>();
            foreach (BoxProperty p in Properties)
            {
                Field f = (Field)p;
                IList<int> related = Properties.Where(x => ((Field)x).GetColor() == f.GetColor() && ((Field)x).GetName() != f.GetName()).Select(x => ((Field)x).GetHouseCount()).ToList<int>();
                if (related.Count > 0)
                    if (f.GetHouseCount() <= related.Min() && this.HasAllFieldsByColor(f.GetColor()))
                        Fields.Add(f);
            }
            return Fields;
        }

        /// <summary>
        /// Pay the bank for any transaction
        /// </summary>
        /// <param name="Amount">Amount of the payment</param>
        public void PayBank(int Amount)
        {
            this.WithdrawCash(Amount);
            Program.WriteLine("Vous payez " + Amount.ToEuro(false) + " à la banque");
        }

        /// <summary>
        /// Check if the player has at least one property
        /// </summary>
        /// <returns><code>true</code> if the player has one or more property</returns>
        public bool HasProperty()
        {
            return this.Properties.Count > 0;
        }

        /// <summary>
        /// Displays the list of player properties order by type and available cards
        /// </summary>
        public void DisplayBoxPropertyList()
        {
            //Properties
            IList<BoxProperty> Fields = this.Properties.Where(p => p is Field).OrderBy(p => p.GetId()).ToList<BoxProperty>();
            IList<BoxProperty> Stations = this.Properties.Where(p => p is Station).OrderBy(p => p.GetId()).ToList<BoxProperty>();
            IList<BoxProperty> Companies = this.Properties.Where(p => p is Company).OrderBy(p => p.GetId()).ToList<BoxProperty>();
            if (Fields.Count > 0)
            {
                Program.WriteLine(Fields.Count > 1 ? "\nTerrains :" : "\nTerrain :", ConsoleColor.White);
                foreach (BoxProperty f in Fields)
                    Program.WriteLine("\t" + f.GetName(true));
            }
            if (Stations.Count > 0)
            {
                Program.WriteLine(Stations.Count > 1 ? "\nGares :" : "\nGare :", ConsoleColor.White);
                foreach (BoxProperty s in Stations)
                    Program.WriteLine("\t" + s.GetName(true));
            }
            if (Companies.Count > 0)
            {
                Program.WriteLine(Companies.Count > 1 ? "\nCompagnies de distr. :" : "\nCompagnie de distr. :", ConsoleColor.White);
                foreach (BoxProperty c in Companies)
                    Program.WriteLine("\t" + c.GetName(true));
            }
            //Cards
            if (this.Cards.Count > 0)
            {
                Program.WriteLine(this.Cards.Count > 1 ? "\nCartes :" : "\nCarte :", ConsoleColor.White);
                foreach (Card c in this.Cards)
                    Program.WriteLine("\t" + c.GetText() + " (" + (c is Chance ? "chance" : "caisse de communauté") + ")");
            }
        }
    }
}
